﻿using System;
using System.Collections.Generic;
using System.Linq;
using NailsFramework.Persistence;
using CID.Domain.Infrastructure;
using CID.Domain.Exceptions;

namespace CID.Domain.Development
{
    public class Version : Model<Version>, IVersion
    {
        public virtual int Id { get; private set; }
        public virtual string Name { get; set; }
        public virtual string Description { get; set; }

        public virtual IComponent Component { get; private set; }
        public virtual IRelease Release { get; private set; }
        public virtual IDeploymentDefinition DeploymentDefinition { get; private set; }
        public virtual IPackage ActivePackage { get; private set; }
        
        private IList<IPackage> packages = new List<IPackage>();
        public virtual IEnumerable<IPackage> Packages { get { return packages; } }

        private IList<IComponent> dependsOn = new List<IComponent>();
        public virtual IEnumerable<IComponent> DependsOn { get { return dependsOn; } }

        protected Version()
        {
            DeploymentDefinition = new DeploymentDefinition(this);
        }

        internal Version(IComponent component, string name, string description) : this()
        {
            Component = component;
            Name = name;
            Description = description;
        }

        public virtual string GetFullName()
        {
            return String.Format("{0} - {1}", Component.Name, Name);
        }

        #region Equals and GetHashCode
        //This class needs to have the Equals overriden to support the comparison with the proxies from the persistence layer
        public override bool Equals(object obj)
        {
            return EntityEqualityHelper.Equals(this, obj);
        }

        public override int GetHashCode()
        {
            return base.GetHashCode();
        }
        #endregion

        public virtual void SetRelease(IRelease release)
        {
            if (Release != null) throw new ParentAlreadyAssignedException("This Version is already assigned to another Release");
            Release = release;
        }

        public virtual void ResetRelease()
        {
            if (Release != null)
            {
                if (Release.Status == ReleaseStatus.Released) throw new InvalidActionForReleaseStatusException("remove Version from Release", ReleaseStatus.Released);
                Release = null;
            }
        }

        public virtual void AddDependency(IComponent component)
        {
            if (Component.Equals(component)) throw new InvalidActionException("set dependency on Version's Component owner");
            if (WillIntroduceCiclicDependency(component)) throw new InvalidActionException("set a ciclic dependency for the Version");
            if (!dependsOn.Contains(component))
            {
                dependsOn.Add(component);
                component.AddDependant(this);
            }
        }

        private bool WillIntroduceCiclicDependency(IComponent component)
        {
            var componentDependsOn = (from IVersion v in component.Versions
                                      from IComponent c in v.DependsOn
                                      select c).Distinct();

            bool result = componentDependsOn.Contains(Component);
            var enumerator = componentDependsOn.GetEnumerator();

            if (!result && enumerator.MoveNext())
                result = WillIntroduceCiclicDependency(enumerator.Current);

            return result;
        }

        public virtual void RemoveDepedency(IComponent component)
        {
            if (dependsOn.Contains(component))
            {
                dependsOn.Remove(component);
                component.RemoveDependant(this);
            }
        }

        public virtual void AddPackage(IPackage package)
        {
            if (!Equals(package.Version)) throw new ParentAlreadyAssignedException("The Package is already assigned for another Version");
            
            if (!packages.Contains(package)) packages.Add(package);            
        }

        public virtual void SetActivePackage(IPackage package)
        {
            if (!packages.Contains(package) || !Equals(package.Version)) throw new InvalidActionException("set this Package as the active one for the Version");
            ActivePackage = package;            
        }

        public virtual IVersion CreateCopy()
        {
            var result = Component.CreateVersion(String.Empty, String.Empty);

            result.DeploymentDefinition.DeploymentTemplate = DeploymentDefinition.DeploymentTemplate;
            foreach (var parameterValue in DeploymentDefinition.DefaultParameterValues)
                parameterValue.CopyTo(result.DeploymentDefinition.GetDefaultParameterValue(parameterValue.Parameter));
            foreach (var parameterValue in DeploymentDefinition.EnvironmentParameterValues)
                parameterValue.CopyTo(result.DeploymentDefinition.AddEnvironmentParameterValue(parameterValue.Parameter, parameterValue.Environment));

            foreach (var dependency in DependsOn)
                result.AddDependency(dependency);

            return result;
        }
    }
}
